package com.legsem.jhosync.util;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.scannotation.AnnotationDB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.legsem.jhosync.ConfigurationException;

public final class ClasspathUtil {

    private final static Logger logger = LoggerFactory
            .getLogger(ClasspathUtil.class);

    /**
     * Utility class.
     */
    private ClasspathUtil() {

    }

    /**
     * Retrieve a set of implementation classes which are annotated with a
     * specified annotation.
     * <p/>
     * 
     * @param annotationClassName the annotation class name that classes should
     *            be decorated with
     * @param devUrls a set of URLs to lookup for annotated implementation
     *            classes
     * @return an array of classes which are decorated with the specified
     *         class-level annotation
     * @throws ConfigurationException if classes cannot be located
     */
    @SuppressWarnings("unchecked")
    public static <T> List < Class < T > > getAnnotatedImplementationClasses(
            final String annotationClassName, final URL[] devUrls)
            throws ConfigurationException {
        try {
            List < Class < T > > classes = new ArrayList < Class < T > >();
            AnnotationDB annotationDB = new AnnotationDB();
            annotationDB.scanArchives(devUrls);
            Set < String > classNames = annotationDB.getAnnotationIndex().get(
                    annotationClassName);
            if (classNames != null) {
                for (String className : classNames) {
                    classes.add((Class < T >) loadClass(className));
                    logger.debug("Found class {} annotated with {}", className,
                            annotationClassName);
                }
            }
            if (classes.size() == 0) {
                logger.warn("No classes annotated with {} in {}",
                        annotationClassName, devUrls);
            }
            return classes;
        } catch (ClassNotFoundException e) {
            throw new ConfigurationException(e);
        } catch (IOException e) {
            throw new ConfigurationException(e);
        }
    }

    /**
     * Rather than using the Class.forName mechanism first, this uses
     * Thread.getContextClassLoader instead. In a Servlet context such as
     * Tomcat, this allows JAXB classes for instance to be loaded from the web
     * application (webapp) location while this code might have been loaded from
     * shared/lib. If Thread.getContextClassLoader fails to locate the class
     * then we give a last chance to Class.forName.
     * 
     * @param qualifiedClassName the class name to load
     * @return the class
     * @throws ClassNotFoundException if class is not accessible from any class
     *             loader
     */
    public static Class < ? > loadClass(final String qualifiedClassName)
            throws ClassNotFoundException {
        ClassLoader contextClassLoader = Thread.currentThread()
                .getContextClassLoader();
        if (contextClassLoader == null) {
            return Class.forName(qualifiedClassName);
        }
        try {
            return contextClassLoader.loadClass(qualifiedClassName);
        } catch (ClassNotFoundException e) {
            return Class.forName(qualifiedClassName);
        }
    }

}
